/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package net.worlds.object;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.TexturePaint;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
import net.worlds.AssetManager;
import net.worlds.WorldsException;
import net.worlds.Main;
import net.worlds.MainView;
import net.worlds.PropChangeHandler;
import net.worlds.Util;
import net.worlds.ui.UI;
import org.jbox2d.collision.shapes.PolygonDef;
import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.common.Vec2D;
import se.krka.kahlua.vm.JavaFunction;
import se.krka.kahlua.vm.LuaCallFrame;


public class Terrain extends GameObject implements Prop{
    private float[] heightmap;
    private PolygonShape[] shapes;
    private int lastHeightmapX;
    private float lastHeightmapY;
    private float clickX;
    private float clickY;
    private BufferedImage image;
    private Paint paint;
    public Terrain() throws WorldsException
    {
        super("Terrain");
        addPropChangeHandler("Heights", new PropChangeHandler() {

            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                String str = value.toString();
                final String[] heights = str.split(",");
                if(!value.equals(""))
                {
                    Main.runInMainThread(new Runnable()
                    {

                        public void run() {
                    if(heightmap == null)
                        initHeightmap();
                            float lastHeight = 0;
                            for(int i = 0; i < heightmap.length; i++)
                            {
                                try
                                {
                                    float height = Float.parseFloat(heights[i]);
                                    heightmap[i] = height;
                                    lastHeight = height;
                                }
                                catch(Exception ex)
                                {
                                    heightmap[i] = lastHeight;
                                }
                            }
                            if(inWorld)
                                drawHeightmap();
                        }

                    });
                }
            }
        });
        addPropChangeHandler("Image", new PropChangeHandler()
        {

            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                final String name = value.toString();
                if(name.equals(""))
                {
                    image = null;
                    paint = null;
                }
                else
                {
                    Util.performRequestWithCallback(
                    new Runnable()
                    {
                        public void run() {
                            try {
                                image = AssetManager.loadImage(name, name);
                            } catch (IOException ex) {
                                throw new RuntimeException("Invalid image URL");
                            }
                        }
                    },
                    new Runnable()
                    {
                        public void run() {

                        }
                    });
                }
            }
        });
        addFunction("generate", new JavaFunction()
        {
            public int call(LuaCallFrame callFrame, int nArguments) {
                generateTerrain();
                Main.runInMainThread(new Runnable()
                {
                    public void run() {
                        drawHeightmap();
                    }
                });
                return 0;
            }
        });
    }
    @Override
    public Object getProp(String key) throws WorldsException
    {
        if(key.equals("Heights"))
        {
            StringBuilder result = new StringBuilder();
            if(heightmap == null)
                return "";
            else
            {
                for(double d : heightmap)
                {
                    result.append(d);
                    result.append(",");
                }
                return result.toString();
            }
        }
        else
            return super.getProp(key);
    }
    @Override
    public void addToWorld() throws WorldsException
    {
        super.addToWorld();
        if(heightmap == null)
            initHeightmap();
        //generateTerrain();
        drawHeightmap();    }
    public void mouseDrag(Vec2D clickVec)
    {
        int heightmapX = (int) clickVec.x / 2;
        int origX;
        float origY;

        if(lastHeightmapX == 0)
            lastHeightmapX = heightmapX;

        if(clickX != 0.0)
        {
            origX = (int) clickX/2;
            origY = clickY;
        }
        else
        {
            origX = lastHeightmapX;
            origY = lastHeightmapY;
        }

        int start;
        int stop;
        float startY;
        float stopY;

        if(origX < heightmapX)
        {
            start = origX;
            stop = heightmapX;
            startY = origY;
            stopY = clickVec.y;
        }
        else
        {
            start = heightmapX;
            stop = origX;
            startY = clickVec.y;
            stopY = origY;
        }
        if(startY < 1)
            startY = 1;
        if(stopY < 1)
            stopY = 1;
        for(int i = start; i < stop + 1; i++)
        {
            if(i >= 0 && i < heightmap.length)
            {
                float d = stop + 1 - start;
                if(d != 0)
                {
                    float r = (i - start)/d;
                    heightmap[i] = startY + (stopY - startY) * r;
                }
                else
                    heightmap[i] = clickVec.y;
            }
        }
        lastHeightmapY = clickVec.y;
        lastHeightmapX = heightmapX;
    }
    public void mouseUp()
    {
        drawHeightmap();
        lastHeightmapX = 0;
        clickX = 0;
        clickY = 0;
    }
    public void mouseDown(Vec2D vec)
    {
        lastHeightmapX = 0;
        if(UI.shiftPressed)
        {
            clickX = vec.x;
            clickY = vec.y;
        }
    }
    private void initHeightmap()
    {
        heightmap = new float[(int) (world.getWidth()/2) + 1];
        for(int i = 0; i < heightmap.length; i++)
            heightmap[i] = 40;
        shapes = new PolygonShape[heightmap.length];
    }
    @Override
    public void destructor()
    {
        for(PolygonShape s : shapes)
            if(s != null)
                world.getGroundBody().destroyShape(s);
    }
    public void generateTerrain()
    {
        Random r = new Random();
        int numLineSegments = heightmap.length;
        ArrayList<LineSegment> result = new ArrayList<LineSegment>(numLineSegments);
        result.add(new LineSegment(0, 0, numLineSegments, 0, 1, 0.6f));
        int currentLevel = 1;
        while(result.size() < numLineSegments)
        {
            Iterator<LineSegment> itr = result.iterator();
            ArrayList<LineSegment> tmp = new ArrayList<LineSegment>(numLineSegments);
            while(itr.hasNext())
            {
                LineSegment l = itr.next();
                if(l.level == currentLevel)
                {
                    LineSegment[] parts = l.subdivide(r);
                    itr.remove();
                    tmp.add(parts[0]);
                    tmp.add(parts[1]);
                }
            }
            result.clear();
            Iterator<LineSegment> tmpItr = tmp.iterator();
            while(tmpItr.hasNext())
            {
                result.add(tmpItr.next());
            }
            currentLevel++;
        }
        for(LineSegment l : result)
        {
            int pointX = (int) l.x1;
            float pointY = (world.getHeight() * ((l.y1 + 1)/2)) - 120;
            if(pointY < 1)
                pointY = 1;
            heightmap[pointX] = pointY;
        }
    }
    @Override
    public void draw(Graphics g) throws WorldsException
    {
        MainView view = Main.getView();
        float worldStartX = Main.getView().getCamPosition().x - (Main.getApplet().getWidth()/2) / view.scaleFactor;
        float worldStopX = Main.getView().getCamPosition().x + (Main.getApplet().getWidth()/2) / view.scaleFactor;
        float worldStartY = Main.getView().getCamPosition().y - (Main.getApplet().getHeight()/2) / view.scaleFactor;
        float worldStopY = Main.getView().getCamPosition().y + (Main.getApplet().getHeight()/2) / view.scaleFactor;
        Vec2D screenStart = new Vec2D(worldStartX, worldStartY).worldToScreen(view);
        Vec2D screenStop = new Vec2D(worldStopX, worldStopY).worldToScreen(view);

        int startX = (int) (2 * Math.round(worldStartX / 2)) - 2;
        int stopX = (int) (2 * Math.round(worldStopX / 2)) + 3;
        if(stopX >= world.getWidth())
            stopX = (int) world.getWidth();
        if(startX < 0)
            startX = 0;
        java.awt.Polygon poly = new java.awt.Polygon();
        Vec2D screenLeft = new Vec2D(0, heightmap[0]).worldToScreen(Main.getView());
        Vec2D screenRight = new Vec2D(world.getWidth(), heightmap[heightmap.length - 1]).worldToScreen(Main.getView());
        for(int i = startX; i <= stopX; i += 2)
        {
            Vec2D screenVec = new Vec2D(i, heightmap[(int) (i/(double)2)]).worldToScreen(Main.getView());
            poly.addPoint((int) screenVec.x, (int) screenVec.y);
        }
        Vec2D startScreen = new Vec2D(startX, 0).worldToScreen(view);
        Vec2D stopScreen = new Vec2D(stopX, 0).worldToScreen(view);
        Vec2D originScreen = Vec2D.ORIGIN.worldToScreen(view);
        poly.addPoint((int) stopScreen.x, (int) stopScreen.y);
        poly.addPoint((int) startScreen.x, (int) startScreen.y);
        g.setColor(getColorProp("Color"));
        Paint originalPaint = ((Graphics2D)g).getPaint();
        if(image != null)
        {
            int w = (int) (image.getWidth() * (Main.getView().scaleFactor/8));
            int h = (int) (image.getWidth() * (Main.getView().scaleFactor / 8));
            ((Graphics2D)g).setPaint(paint = new TexturePaint(image, new Rectangle((int) originScreen.x, (int) originScreen.y - h, w, h)));
        }
        if(Main.getLocalPlayer() != null)
        {
            if(startX == 0)
            {
                g.fillRect(0, (int) screenLeft.y, (int) screenLeft.x, Main.getApplet().getHeight() - (int) screenLeft.y);
            }
            if(stopX == (int) world.getWidth())
            {
                g.fillRect((int) screenRight.x, (int) screenRight.y, Main.getApplet().getWidth() - (int) screenRight.x, Main.getApplet().getHeight() - (int) screenRight.y);
            }
            if(worldStartY < 0)
            {
                g.fillRect((int) screenStart.x, (int) originScreen.y, Main.getApplet().getWidth(), Main.getApplet().getWidth() - (int) originScreen.y);
            }
        }
        g.fillPolygon(poly);
        if(image != null)
            ((Graphics2D)g).setPaint(originalPaint);
    }
    private void drawHeightmap()
    {
        for(int i = 0; i < heightmap.length; i++)
        {
            PolygonDef def = new PolygonDef();
            def.addVertex(new Vec2D(i * 2 + 0.0f, 0));
            def.addVertex(new Vec2D((i + 1) * 2 - 0.0f, 0));
            def.density = 1;
            def.friction = 0.8f;
            def.restitution = 0.4f;
            def.isTerrain = true;
            if(i < heightmap.length - 1)
            {
                def.addVertex(new Vec2D((i + 1) * 2 - 0.0f, (float) heightmap[i + 1]));
                def.addVertex(new Vec2D(i * 2 + 0.0f, (float) heightmap[i]));
                if(shapes[i] != null)
                    world.getGroundBody().destroyShape(shapes[i]);
                shapes[i] = (PolygonShape) world.getGroundBody().createShape(def);

            }
        }
    }
}
class LineSegment {
    public float x1;
    public float y1;
    public float x2;
    public float y2;
    public int level;
    private float range;
    public LineSegment(float x1, float y1, float x2, float y2, int level, float roughness)
    {
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
        this.level = level;
        this.range = roughness;
    }
    public LineSegment[] subdivide(Random r)
    {
        float midpoint = (x1 + x2)/2;
        LineSegment[] t = new LineSegment[2];
        float rand;

        if(level == 1)
        {
            rand = 0;
        }
        else
        {
            float n = r.nextFloat();
            rand = ((y1+y2)/2) + (n * (range * 2) - range);
        }
        float l1x1 = x1;
        float l1y1 = y1;
        float l1x2 = midpoint;
        float l1y2 = rand;
        float l2x1 = midpoint;
        float l2y1 = rand;
        float l2x2 = x2;
        float l2y2 = y2;
        float factor = 0.55f;
        LineSegment l1 = new LineSegment(l1x1, l1y1, l1x2, l1y2, level + 1, range * factor);
        LineSegment l2 = new LineSegment(l2x1, l2y1, l2x2, l2y2, level + 1, range * factor);
        t[0] = l1;
        t[1] = l2;
        return t;
    }
}